package supervise;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.StringTokenizer;

import utils.Review;
import utils.StopWords;

/**
 * Un classifieur binomial
 * 
 */
public class ClassifieurBinomial extends Classifieur {

	private HashMap<String, Double> betaPositif;
	private HashMap<String, Double> betaNegatif;

	/**
	 * Crée un classifieur binomial qui construit son dictionnaire et apprend à
	 * partir de l'ensemble donné
	 * 
	 * @param reviews
	 *            Liste de Reviews sur lesquelles construire son dictionnaire et
	 *            apprendre
	 * @param stopWords
	 *            Choix d'une liste de mots-outils à ignonrer.
	 * @param frequenceMaxi
	 *            La fréquence maximale d'apparition des mots, au delà de
	 *            laquelle le mot ne sera pas ajouté au dictionnaire. La
	 *            fréquence est calculée sur tous les mots du texte sauf les
	 *            mots-outils.
	 * @param parametreLissage
	 *            Paramètre de lissage de Laplace. Usuellement un petit epsilon.
	 */
	public ClassifieurBinomial(ArrayList<Review> reviews, StopWords stopWords,
			double frequenceMaxi, double parametreLissage) {
		String stopWordsPath = null;
		if (stopWords == StopWords.MEDIAWIKI) {
			stopWordsPath = "MediaWikiStopwords";
		} else if (stopWords == StopWords.SHORT) {
			stopWordsPath = "ShortStopwords";
		} else if (stopWords == StopWords.MEDIUM) {
			stopWordsPath = "MediumStopwords";
		} else if (stopWords == StopWords.LONG) {
			stopWordsPath = "LongStopwords";
		}
		construireDictionnaire(reviews, stopWordsPath, frequenceMaxi);
		apprendre(reviews, parametreLissage);
	}

	/**
	 * Crée les paramètres beta pour les 2 classes à partir de la liste de
	 * review donnée
	 * 
	 * @param reviews
	 *            Liste des reviews sur lesquelles apprendre
	 * @param parametreLissage
	 *            Paramètre de lissage de Laplace. Usuellement un petit epsilon.
	 */
	private void apprendre(ArrayList<Review> reviews, double parametreLissage) {
		ArrayList<String> dicoFichier = new ArrayList<String>();
		int nbPositives = 0, nbNegatives = 0;
		boolean isPositive;
		String token;

		// On met tout le dictionnaire dans chaque vecteur
		betaPositif = new HashMap<String, Double>(dictionnaire.size());
		betaNegatif = new HashMap<String, Double>(dictionnaire.size());
		for (String mot : dictionnaire) {
			betaPositif.put(mot, 0.d);
			betaNegatif.put(mot, 0.d);
		}

		// Pour chaque review
		for (Review review : reviews) {
			StringTokenizer tokenizer = new StringTokenizer(review.getTexte(),
					" ");
			// Pour chaque mot
			while (tokenizer.hasMoreTokens()) {
				token = tokenizer.nextToken().toLowerCase();
				// S'il appartient au dico
				if (dictionnaire.contains(token)) {
					// Et qu'on ne l'a pas déjà vu dans ce fichier
					if (!dicoFichier.contains(token)) {
						// On l'ajoute a la liste de mots du fichier
						dicoFichier.add(token);
					}
				}
			}// tous les tokens

			isPositive = review.isOpinionPositive();
			// Pour chaque mot rencontré
			for (String mot : dicoFichier) {
				// Si c'était dans un avis positif
				if (isPositive) {
					// On le compte pour betaPos
					betaPositif.put(mot, betaPositif.get(mot) + 1.d);
				}
				// Dans un avis negatif
				else {
					// On le compte pour betaNeg
					betaNegatif.put(mot, betaNegatif.get(mot) + 1.d);
				}
			}

			// Si la review etait positive
			if (isPositive) {
				++nbPositives;
			} else {
				++nbNegatives;
			}

			dicoFichier.clear();
		}// toutes les reviews

		// On normalise
		for (String mot : betaNegatif.keySet()) {
			betaNegatif.put(mot, betaNegatif.get(mot) * nbPositives
					/ nbNegatives);
		}

		// Et on convertit en vrais parametres beta, entre 0 et 1
		for (String mot : betaPositif.keySet()) {
			betaPositif.put(mot, (betaPositif.get(mot) + parametreLissage)
					/ (nbPositives + 10 * parametreLissage));
		}
		for (String mot : betaNegatif.keySet()) {
			betaNegatif.put(mot, (betaNegatif.get(mot) + parametreLissage)
					/ (nbNegatives + 10 * parametreLissage));
		}
		/*
		 * System.out.println(nbPositives + " avis positifs, " + nbNegatives +
		 * " avis negatifs"); System.out.println("motmot\tpositif\tnegatif");
		 * for(String mot : dictionnaire) { System.out.println(mot + "\t" +
		 * betaPositif.get(mot) + "\t" + betaNegatif.get(mot)); }
		 */
	}

	/**
	 * Evalue les probabilités d'une liste de reviews
	 * 
	 * @param reviews
	 *            La liste des reviews à évaluer
	 * @return Une map contenant comme clé l'identifiant de la review évaluée,
	 *         et comme valeur true si la review est évaluée positive, false si
	 *         elle est évaluée négative
	 */
	public HashMap<Integer, Boolean> classifier(ArrayList<Review> reviews) {
		HashMap<Integer, Boolean> resultat = new HashMap<Integer, Boolean>();
		ArrayList<String> dicoFichier = new ArrayList<String>();
		String token;
		float decision;

		// Pour chaque review
		for (Review review : reviews) {
			StringTokenizer tokenizer = new StringTokenizer(review.getTexte());
			// Pour tous les mots
			while (tokenizer.hasMoreTokens()) {
				token = tokenizer.nextToken().toLowerCase();
				// S'il appartient au dictionnaire
				if (dictionnaire.contains(token)) {
					// S'il n'est pas encore dans la liste des mots de l'avis
					if (!dicoFichier.contains(token)) {
						// On l'ajoute
						dicoFichier.add(token);
					}
				}
			}// tous les mots

			decision = 0;
			// Pour tous les mots du dictionnaire
			for (String mot : dictionnaire) {
				// Si il est dans l'avis
				if (dicoFichier.contains(mot)) {
					decision += Math.log(betaPositif.get(mot)
							/ betaNegatif.get(mot));
				}
				// Pas dans l'avis
				else {
					decision += Math.log((1 - betaPositif.get(mot))
							/ (1 - betaNegatif.get(mot)));
				}
			}
			dicoFichier.clear();

			// Si c'est un positif
			if (decision >= 0) {
				resultat.put(review.getId(), true);
			} else {
				resultat.put(review.getId(), false);
			}

		} // toutes les reviews
		return resultat;
	}
}
